#!/usr/bin/env bash
# This script can not be executed directly, it is baked in the 
# openyurt/yurtctl-servant, before exeuction, context value (i.e., {{.VARIABLE}})
# need to be replaced based on the environment variables set in the pod, 
# and will be executed as a subprogram of the nsenter command.

set -o errexit
set -o pipefail

KUBELET_CONF=${KUBELET_CONF:-/etc/kubernetes/kubelet.conf}
KUBELET_SVC=${KUBELET_SVC:-/etc/systemd/system/kubelet.service.d/10-kubeadm.conf}
KUBELET_CLI_PEM=${KUBELET_CLI_PEM:-/var/lib/kubelet/pki/kubelet-client-current.pem}
BOOTSTRAP_KUBELET_CONF=${BOOTSTRAP_KUBELET_CONF:-/etc/kubernetes/bootstrap-kubelet.conf}
OPENYURT_DIR=${OPENYURT_DIR:-/var/lib/openyurt}
STATIC_POD_PATH=${STATIC_POD_PATH:-/etc/kubernetes/manifests}
MINIKUBE_PKI_DIR=${MINIKUBE_PKI_DIR:-/var/lib/minikube/certs}
ACTION=$1
PROVIDER=$2

# PROVIDER can be nounset
set -o nounset

declare -r YURTHUB_TEMPLATE='
apiVersion: v1
kind: Pod
metadata:
  labels:
    k8s-app: yurt-hub
  name: yurt-hub
  namespace: kube-system
spec:
  volumes:
  - name: pki
    hostPath:
      path: __pki_path__
      type: Directory
  - name: kubernetes
    hostPath:
      path: /etc/kubernetes
      type: Directory
  - name: pem-dir
    hostPath:
      path: /var/lib/kubelet/pki
      type: Directory
  containers:
  - name: yurt-hub
    image: __yurthub_image__
    imagePullPolicy: Always
    volumeMounts:
    - name: kubernetes
      mountPath: /etc/kubernetes
    - name: pki
      mountPath: /etc/kubernetes/pki
    - name: pem-dir
      mountPath: /var/lib/kubelet/pki
    command:
    - yurthub
    - --v=2
    - --server-addr=https://$(KUBERNETES_SERVICE_HOST):$(KUBERNETES_SERVICE_PORT_HTTPS)
    - --node-name=$(NODE_NAME)
    livenessProbe:
      httpGet:
        host: 127.0.0.1
        path: /v1/healthz
        port: 10261
      initialDelaySeconds: 300
      periodSeconds: 5
      failureThreshold: 3
    resources:
      requests:
        cpu: 150m
        memory: 150Mi
      limits:
        memory: 300Mi
    securityContext:
      capabilities:
        add: ["NET_ADMIN", "NET_RAW"]
    env:
    - name: NODE_NAME
      valueFrom:
        fieldRef:
          fieldPath: spec.nodeName
  hostNetwork: true
  priorityClassName: system-node-critical
  priority: 2000001000
'

# log outputs the log message with date and program prefix
log() {
    echo "$(date +"%m/%d/%Y-%T-%Z") [YURT_SERVANT] [LOG] $@"
}

# error outputs the error message with data program prefix
error() {
    echo "$(date +"%m/%d/%Y-%T-%Z") [YURT_SERVANT] [ERROR] $@"
}

# preset creates the /var/lib/kubelet/pki/kubelet-client-current.pem if 
# it does not exist
preset() {
    # if $KUBELET_CLI_PEM doesn't exist, create one based on 'client-certificate-data'
    # and 'client-key-data' of $KUBELET_CONF
    if [ -f $KUBELET_CLI_PEM ]; then
        log "$KUBELET_CLI_PEM exist"
    else 
        log "$KUBELET_CLI_PEM does not exist"
        grep 'client-certificate-data' $KUBELET_CONF | awk '{print $2}' | 
            base64 -d > $KUBELET_CLI_PEM 
        grep 'client-key-data' $KUBELET_CONF | awk '{print $2}' | 
            base64 -d >> $KUBELET_CLI_PEM
        log "$KUBELET_CLI_PEM is created"
    fi
}

# setup_yurthub sets up the yurthub pod and wait for the its status to be Running
setup_yurthub() {
    provider=$1
    # put yurt-hub yaml to /etc/kubernetes/manifests 
    if [ "$provider" == "minikube" ]; then
        log "setting up yurthub on nodes of minikube"
        yurthub_yaml=$(echo "$YURTHUB_TEMPLATE" | 
            sed 's|__pki_path__|/var/lib/minikube/certs|')
    else
        log "setting up yurthub on nodes of ack"
        yurthub_yaml=$(echo "$YURTHUB_TEMPLATE" | 
            sed 's|__pki_path__|/etc/kubernetes/pki|')
    fi 
    echo "$yurthub_yaml" > ${STATIC_POD_PATH}/yurt-hub.yaml
    log "create the ${STATIC_POD_PATH}/yurt-hub.yaml"
    # wait yurthub pod to be ready
    local retry=5
    while [ $retry -ge 0 ] 
    do
        sleep 10
        # NOTE: context variables need to be replaced before exeuction
        local podPhase

        if [ "$provider" == "ack" ]; then
            # the ack's apiserver doesn't support anonymous user visit, use 
            # kubectl to check the pod status
            podPhase=$(kubectl get po/yurt-hub-__node_name__\
                --kubeconfig $KUBELET_CONF -n kube-system |
                awk 'NR>1{print $3}') 
        else
            local podStat=$(curl -s \
                https://__kubernetes_service_host__:__kubernetes_service_port_https__\
/api/v1/namespaces/kube-system/pods/yurt-hub-__node_name__ \
--cert /var/lib/kubelet/pki/kubelet-client-current.pem \
--key /var/lib/kubelet/pki/kubelet-client-current.pem \
--cacert ${MINIKUBE_PKI_DIR}/ca.crt)
            # yurt-hub pod is not found
            if [ -n "$(echo "$podStat" | grep NotFound)" ]; then                 
                retry=$((retry-1))
	            log "yurt-hub-$NODE_NAME is not found, will retry $retry times"
                continue
            fi
            # yurt-hub pod is created, let's check if the phase is "running"
            podPhase=$(echo "$podStat" | 
                grep '\"phase\":' | 
                awk -F ':' '{print $2}' | 
                tr -d '", ')
        fi

        
        if [ "$podPhase" == "Running" ]; then 
            log "yurt-hub-$NODE_NAME is $podPhase"
            return 
        else 
            retry=$((retry-1))
            if [ $retry -ge 0 ]; then
                log "yurt-hub-$NODE_NAME is $podPhase, will retry $retry times"
            else 
                error "yurt-hub-$NODE_NAME failed, after retry 5 times"
                exit 1
            fi
            continue
        fi
    done
}

# reset_kubelet changes the configuration of the kubelet service and restart it
reset_kubelet() {
    # create a working dir to store revised kubelet.conf 
    mkdir -p $OPENYURT_DIR
    cp $KUBELET_CONF $OPENYURT_DIR/    
    # revise the copy of the kubelet.conf
    sed -i '/certificate-authority-data/d;
    /client-key/d;
    /client-certificate/d;
    /user:/d;
    s/ https.*/ http:\/\/127.0.0.1:10261/g' $OPENYURT_DIR/kubelet.conf
    log "generated the revised kubeconfig $OPENYURT_DIR/kubelet.conf"
    # revise the kubelet.service drop-in 
    if [ -f $BOOTSTRAP_KUBELET_CONF ]; then
        # /etc/kubernetes/bootstrap-kubelet.config exist, keep the 
        # --bootstrap-kubeconfig option
        sed -i "s|--kubeconfig=.*kubelet.conf|--kubeconfig=$OPENYURT_DIR\/kubelet.conf|g" $KUBELET_SVC
    else
        sed -i "s/--bootstrap.*bootstrap-kubelet.conf//g;
        s|--kubeconfig=.*kubelet.conf|--kubeconfig=$OPENYURT_DIR\/kubelet.conf|g" $KUBELET_SVC
    fi
    log "revised the kubelet.service drop-in file"
    # reset the kubelete.service
    systemctl daemon-reload
    systemctl restart kubelet.service
    log "kubelet has been restarted"
}

# remove_yurthub deletes the yurt-hub pod
remove_yurthub() {
    # remove the yurt-hub.yaml to delete the yurt-hub 
    [ -f $STATIC_POD_PATH/yurt-hub.yaml ] &&
        rm $STATIC_POD_PATH/yurt-hub.yaml
    log "yurt-hub has been removed"
}

# revert_kubelet resets the kubelet service and makes it connect to the 
# apiserver directly
revert_kubelet() {
    # remove openyurt's kubelet.conf if exist
    [ -f $OPENYURT_DIR/kubelet.conf ] && rm $OPENYURT_DIR/kubelet.conf
    # revise the kubelet.service drop-in
    sed -i "s|--kubeconfig=.*kubelet.conf|--kubeconfig=$KUBELET_CONF|g;" $KUBELET_SVC
    log "revised the kubelet.service drop-in file back to the default"
    # reset the kubelete.service
    systemctl daemon-reload
    systemctl restart kubelet.service
    log "kubelet has been reset back to default"
}

case $ACTION in
    convert)
        preset
        setup_yurthub $PROVIDER
        reset_kubelet
        ;;
    revert)
        revert_kubelet 
        remove_yurthub
        ;;
    *)
        error "unknwon action $ACTION"
        exit 1
        ;;
esac



log "done"
